﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Collections;
using System.ComponentModel;
using System.Windows.Data;
using System.Windows.Input;

namespace Kokomo.Controls
{
	[TemplatePart(Name = SuggestionListPartName, Type = typeof(Selector))]
	public class AutoCompleteBox : TextBox
	{
		public const string SuggestionListPartName = "PART_SuggestionList";

		static AutoCompleteBox()
		{
			DefaultStyleKeyProperty.OverrideMetadata(typeof(AutoCompleteBox), new FrameworkPropertyMetadata(typeof(AutoCompleteBox)));
		}

		public AutoCompleteBox()
		{
		}

		#region Data


		public IEnumerable ItemsSource
		{
			get { return (IEnumerable)GetValue(ItemsSourceProperty); }
			set { SetValue(ItemsSourceProperty, value); }
		}

		// Using a DependencyProperty as the backing store for ItemsSource.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty ItemsSourceProperty = Selector.ItemsSourceProperty.AddOwner(typeof(AutoCompleteBox));

		public DataTemplate ItemTemplate
		{
			get { return (DataTemplate)GetValue(ItemTemplateProperty); }
			set { SetValue(ItemTemplateProperty, value); }
		}

		// Using a DependencyProperty as the backing store for ItemTemplate.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty ItemTemplateProperty = ItemsControl.ItemTemplateProperty.AddOwner(typeof(AutoCompleteBox));

		public Style ItemContainerStyle
		{
			get { return (Style)GetValue(ItemContainerStyleProperty); }
			set { SetValue(ItemContainerStyleProperty, value); }
		}

		// Using a DependencyProperty as the backing store for ItemContainerStyle.  This enables animation, styling, binding, etc...
		public static readonly DependencyProperty ItemContainerStyleProperty = ItemsControl.ItemContainerStyleProperty.AddOwner(typeof(AutoCompleteBox));



		public string MatchText
		{
			get { return (string)GetValue(MatchTextProperty); }
			set { SetValue(MatchTextProperty, value); }
		}

		// Using a DependencyProperty as the backing store for MatchText.  This enables animation, styling, binding, etc...
		private static readonly DependencyProperty MatchTextProperty =
			DependencyProperty.Register("MatchText", typeof(string), typeof(AutoCompleteBox), new FrameworkPropertyMetadata(null));
		//public static readonly DependencyProperty MatchTextProperty = MatchTextPropertyKey.DependencyProperty;

		#endregion

		#region Templating
		public override void OnApplyTemplate()
		{
			base.OnApplyTemplate();

			this.SuggestionListPart = this.Template.FindName(SuggestionListPartName, this) as Selector;
		}
		private Selector _suggestionListPart;
		private Selector SuggestionListPart
		{
			get { return this._suggestionListPart; }
			set
			{
				if (this._suggestionListPart != null)
				{
					this._suggestionListPart.SelectionChanged -= new SelectionChangedEventHandler(SuggestionListPart_SelectionChanged);
				}
				this._suggestionListPart = value;
				if (value != null)
				{
					value.SelectionChanged += new SelectionChangedEventHandler(SuggestionListPart_SelectionChanged);
				}
			}
		}
		#endregion



		public bool IsSuggestionListOpen
		{
			get { return (bool)GetValue(IsSuggestionListOpenProperty); }
			private set { SetValue(IsSuggestionListOpenPropertyKey, value); }
		}

		// Using a DependencyProperty as the backing store for IsSuggestionListOpen.  This enables animation, styling, binding, etc...
		private static readonly DependencyPropertyKey IsSuggestionListOpenPropertyKey =
			DependencyProperty.RegisterReadOnly("IsSuggestionListOpen", typeof(bool), typeof(AutoCompleteBox), new UIPropertyMetadata(false));

		public static readonly DependencyProperty IsSuggestionListOpenProperty = IsSuggestionListOpenPropertyKey.DependencyProperty;

		private bool _isSuggesting;
		void SuggestionListPart_SelectionChanged(object sender, SelectionChangedEventArgs e)
		{
			object selectedItem = this.SuggestionListPart.SelectedItem;
			if (selectedItem != null)
			{
				string suggestionText = this.SuggestionItemToString(selectedItem);
				string currentText = this.MatchText;

				if (!string.IsNullOrEmpty(suggestionText))
				{
					this._isSuggesting = true;
					this.Text = suggestionText;
					if (suggestionText.Length >= currentText.Length)
					{
						this.SelectionStart = currentText.Length;
						this.SelectionLength = suggestionText.Length - currentText.Length;
					}
					this._isSuggesting = false;
				}
			}
		}

		private string SuggestionItemToString(object item)
		{
			return (item == null) ? string.Empty : item.ToString();
		}

		private bool Matches(object x, string pattern)
		{
			if (x == null) return false;
			string str = SuggestionItemToString(x);

			return str.Contains(pattern, StringComparison.InvariantCultureIgnoreCase);
		}

		protected override void OnTextChanged(TextChangedEventArgs e)
		{
			base.OnTextChanged(e);
			this.UpdateMatchText();
		}

		protected override void OnSelectionChanged(RoutedEventArgs e)
		{
			base.OnSelectionChanged(e);
			this.UpdateMatchText();
		}

		protected void UpdateMatchText()
		{
			if (this._isSuggesting) return;

			string text = this.Text;

			string matchText = null;
			if (
				!string.IsNullOrEmpty(text)
				)
			{
				matchText = (this.CaretIndex > 0)
					? text.Substring(0, this.CaretIndex)
					: text
					;
			}
			this.MatchText = matchText;
		}

		private bool IsNavigationKey(Key key)
		{
			bool isNavigation = false;
			switch (key)
			{
				case Key.Up:
				case Key.Down:
					isNavigation = true;
					break;
			}

			return isNavigation;
		}
		protected override void OnPreviewKeyDown(System.Windows.Input.KeyEventArgs e)
		{
			if (
				(e.Key == Key.Enter)
				&& this.IsSuggestionListOpen
				)
			{
				this.IsSuggestionListOpen = false;
				this.CaretIndex = this.Text.Length;
				e.Handled = true;
			}
			else if (
				(e.Key == Key.Tab)
				&& this.IsSuggestionListOpen
				)
			{
				this.CaretIndex = this.Text.Length;
				e.Handled = true;
			}
			else if (
				this.IsNavigationKey(e.Key)
				&& (this.SuggestionListPart != null)
				)
			{
				int selectionIndex = this.SuggestionListPart.SelectedIndex;
				int itemCount = this.SuggestionListPart.Items.Count;

				if (selectionIndex == -1)
				{
					selectionIndex = 0;
				}
				else
				{
					switch (e.Key)
					{
						case Key.Up:
							selectionIndex--;
							if (selectionIndex < 0) selectionIndex = itemCount - 1;
							break;
						case Key.Down:
							selectionIndex++;
							if (selectionIndex >= itemCount) selectionIndex = 0;
							break;
					}
				}

				if (
					(selectionIndex >= 0)
					&& (selectionIndex < itemCount)
					)
				{
					this.SuggestionListPart.SelectedIndex = selectionIndex;
				}

				e.Handled = true;
			}
			else
			{
				base.OnPreviewKeyDown(e);
			}
		}

		protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e)
		{
			base.OnGotKeyboardFocus(e);

			this.IsSuggestionListOpen = true;
		}
		protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
		{
			base.OnLostKeyboardFocus(e);

			this.IsSuggestionListOpen = false;
		}
	}
}
